package com.leyou.search.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.utils.NumberUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate template;


    @Override
    public Goods buildGoods(SpuBo spu) {

        Long spuId = spu.getId();

        // 查询分类
        List<Long> ids = Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3());
        List<Category> categories = categoryClient.queryCategoryByIds(ids);
        if (CollectionUtils.isEmpty(categories)) {
            throw new LyException(ExceptionEnum.CATEGORY_NOT_FOUND);
        }
        List<String> names = categories.stream().map(Category::getName).collect(Collectors.toList());

        // 查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());
        if (brand == null) {
            throw new LyException(ExceptionEnum.BRAND_NOT_FOUND);
        }
        String all = spu.getTitle() + StringUtils.join(names, " ") + brand.getName();

        // 查询sku
        List<Sku> skus = goodsClient.querySkuBySpuId(spuId);
        if (CollectionUtils.isEmpty(skus)) {
            throw new LyException(ExceptionEnum.QUERY_SKU_NOT_FOUND);
        }
        Set<Long> prices = new HashSet<>();
        List<Map<String, Object>> skusMap = new ArrayList<>();
        for (Sku sku : skus) {
            prices.add(sku.getPrice());
            HashMap<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            map.put("price", sku.getPrice());
            skusMap.add(map);
        }
        String skuJson = JsonUtils.toString(skusMap);

        // 查询规格参数的名称
        List<SpecParam> params = specificationClient.querySpecParamByCid(null, spu.getCid3(), true, null);
        if (CollectionUtils.isEmpty(params)) {
            throw new LyException(ExceptionEnum.SPEC_PARAM_NOT_FOUND);
        }

        // 查询Spu详情
        SpuDetail spuDetail = goodsClient.querySpuDetailBySpuId(spuId);

        // 通用规格参数
        Map<Long, String> genericSpecMap = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, String.class);

        // 特有规格参数
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });


        // specs
        Map<String, Object> specs = new HashMap<String, Object>();

        for (SpecParam param : params) {
            String specName = param.getName();
            Object value = "";
            if (param.getGeneric()) {
                value = genericSpecMap.get(param.getId());
                if (param.getNumeric()) {
                    value = chooseSegment(value.toString(), param);
                }
            } else {
                value = specialSpecMap.get(param.getId());
            }

            value = value == null || StringUtils.isEmpty(value.toString()) ? "其他" : value;
            specs.put(specName, value);
        }


        Goods goods = new Goods();
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setId(spuId);
        goods.setSubTitle(spu.getSubTitle());

        goods.setAll(all); // 搜索字段,包含标题,分类,品牌,规格等
        goods.setPrice(prices); // 所有sku价格的集合
        goods.setSkus(skuJson); // 所有sku的集合的json格式
        goods.setSpecs(specs); // 所有的可搜索的规格参数

        return goods;
    }

    @Override
    public PageResult<Goods> search(SearchRequest searchRequest) {

        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.QUERY_SKU_NOT_FOUND);
        }

        // 查询构建器
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();

        // 结果集过滤
        searchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));

//        QueryBuilder basicQuery = QueryBuilders.matchQuery("all", key);
        QueryBuilder basicQuery = buildBasicQuery(searchRequest);
        // 查询条件
        searchQueryBuilder.withQuery(basicQuery);

        // 排序
        String sortBy = searchRequest.getSortBy();
        Boolean desc = searchRequest.getDescending();
        if (StringUtils.isNotBlank(sortBy)) {
            // 如果不为空,则进行排序
            searchQueryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc ? SortOrder.DESC : SortOrder.ASC));
        }

        // 分页
        Integer page = searchRequest.getPage() - 1;
        Integer size = searchRequest.getSize();
        searchQueryBuilder.withPageable(PageRequest.of(page, size));


        // 聚合
        String categoryAggName = "category_agg";
        String brandAggName = "brand_agg";
        searchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        searchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        // 查询聚合分页结果
        AggregatedPage<Goods> goods = template.queryForPage(searchQueryBuilder.build(), Goods.class);

        // 解析分页数据
        int totalPages = goods.getTotalPages();
        long totalElements = goods.getTotalElements();
        List<Goods> content = goods.getContent();

        // 解析聚合数据
        Aggregations aggregations = goods.getAggregations();

        // 通过索引库查询分类数据
        List<Category> categories = parseCategoryAgg(aggregations.get(categoryAggName));
        // 通过索引库查询品牌数据
        List<Brand> brands = parseBrandAgg(aggregations.get(brandAggName));

        List<Map<String, Object>> specs = null;
        // 规格参数
        if (categories != null && categories.size() == 1) {

            specs = buildSpecs(categories.get(0).getId(), basicQuery);
        }

        return new SearchResult(totalElements, totalPages, content, categories, brands, specs);
    }

    @Override
    public void insertOrUpdateBySpuId(Long spuId) {

        SpuBo spu = goodsClient.querySpuBySpuId(spuId);
        if (spu == null) {
            log.error("索引对应的spu不存在,spuId: {}", spuId);
            // 抛出异常,让消息回滚
            throw new RuntimeException();
        }
        Goods goods = buildGoods(spu);
        goodsRepository.save(goods);
    }

    @Override
    public void deleteBySpuId(Long spuId) {
        goodsRepository.deleteById(spuId);
    }

    private QueryBuilder buildBasicQuery(SearchRequest searchRequest) {

        // 构建布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()));

        // 过滤条件
        Map<String, String> map = searchRequest.getFilter();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            if (!"cid3".equals(key) && !"brandId".equals(key)) {
                key = "specs." + key + ".keyword";
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }

        return boolQueryBuilder;

    }

    private List<Map<String, Object>> buildSpecs(Long cid, QueryBuilder basicQuery) {

        List<Map<String, Object>> specs = new ArrayList<>();
        // 根据分类 查询可搜索的规格参数
        List<SpecParam> params = specificationClient.querySpecParamByCid(null, cid, true, null);
        // 准备查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(basicQuery);
        // 限制查询数据量
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 聚合条件
        for (SpecParam param : params) {
            // 获取规格参数名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name + ".keyword"));
        }
        // 得到聚合结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
        // 解析结果
        Aggregations aggs = result.getAggregations();

        for (SpecParam param : params) {
            String name = param.getName();
            StringTerms terms = aggs.get(name);
            List<String> options = terms.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList());
            // 准备 规格参数的map
            Map<String, Object> map = new HashMap<>();
            map.put("k", name);
            map.put("options", options);
            specs.add(map);
        }
        return specs;
    }

    private List<Brand> parseBrandAgg(LongTerms brandTerms) {
        try {
            List<Long> bids = brandTerms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Brand> brands = brandClient.queryBrandByIds(bids);
            return brands;
        } catch (Exception e) {
            log.error("[搜索服务: 查询品牌失败]", e);
            return null;
        }
    }

    private List<Category> parseCategoryAgg(LongTerms categoryTerms) {
        try {
            List<Long> cids = categoryTerms.getBuckets().stream().map(c -> c.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Category> categories = categoryClient.queryCategoryByIds(cids);
            return categories;
        } catch (Exception e) {
            log.error("[搜索服务: 查询分类失败]", e);
            return null;
        }
    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }
}
